﻿using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using Point = System.Windows.Point;
using Image = System.Windows.Controls.Image;
using Size = System.Windows.Size;
using System.Windows.Data;
using System.Drawing;
using System.Windows.Interop;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
using System.Linq;

namespace SuperFrame.Charts
{
    public class ContainerCollection : Panel
    {
        public CollectionSeries Series
        {
            get { return (CollectionSeries)GetValue(SeriesProperty); }
            set { SetValue(SeriesProperty, value); }
        }
        public static readonly DependencyProperty SeriesProperty = DependencyProperty.Register("Series", typeof(CollectionSeries), typeof(ContainerCollection), new PropertyMetadata(null));

        protected bool isDirectXInitialized;
        private int bpp = PixelFormats.Bgra32.BitsPerPixel / 8;
        private IntPtr MapViewPointer;
        protected Graphics GDIGraphics;
        protected InteropBitmap InteropBitmap;
        internal double dpiFactor;
        protected WriteableBitmap WritableBitmap;
        protected Bitmap ImageBitmap;
        protected Graphics WritableBitmapGraphics;


        internal ContainerAxisCrossLines axisLinesconatiner;
        bool isIntialized;

        private const uint FILE_MAP_ALL_ACCESS = 0xF001F;
        private const uint PAGE_READWRITE = 0x04;



        public Containers Containers
        {
            get { return (Containers)GetValue(ContainersProperty); }
            set { SetValue(ContainersProperty, value); }
        }
        Image bitmapImage;
        public static readonly DependencyProperty ContainersProperty =DependencyProperty.Register("Containers", typeof(Containers), typeof(ContainerCollection), new PropertyMetadata(null));

        public ContainerCollection()
        {
            Containers = new Containers();
            this.SizeChanged += ContainerCollection_SizeChanged;
            bitmapImage = new Image();
            bitmapImage.Stretch = Stretch.None;


        }

        void ContainerCollection_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            Refresh();
        }

        public void Refresh()
        {

            switch (RenderingMode)
            {
                case RenderingMode.GDIRendering:
                    if (this.InteropBitmap != null && this.GDIGraphics != null)
                    {
                        this.GDIGraphics.Dispose();
                        this.GDIGraphics = null;
                        this.InteropBitmap = null;
                        GC.Collect();
                    }
                    break;
               case RenderingMode.WritableBitmap:
                    if (this.WritableBitmap != null && this.WritableBitmapGraphics != null)
                    {
                        this.WritableBitmapGraphics.Dispose();
                        this.WritableBitmapGraphics = null;
                        this.WritableBitmap = null;
                        GC.Collect();
                    }
                    break;
                case RenderingMode.Default:
                    break;

                default:
                    break;
            }

            Initialize();
            if (!isIntialized)
                GenerateConatiners();
            else
                UpdateContainers();

            if (this.XAxis != null)
            {
                this.XAxis.CalculateIntervalFromSeriesPoints();
                this.XAxis.Refresh();
            }
            if (this.YAxis != null)
            {
                this.YAxis.CalculateIntervalFromSeriesPoints();
                this.YAxis.Refresh();
            }

            foreach (BaseSeries series in this.Series)
            {
                if (Containers.Count > 0)
                    series.seriesContainer = Containers[series.Index];
                series.Refresh();
            }

        }

        public void Clear()
        {
            try
            {
                switch (this.RenderingMode)
                {
                    case RenderingMode.GDIRendering:
                        GDIGraphics.Clear(System.Windows.Media.Colors.Transparent.AsDrawingColor());
                        break;
                    case RenderingMode.WritableBitmap:
                        WritableBitmapGraphics.Clear(System.Windows.Media.Colors.Transparent.AsDrawingColor());
                        break;
                    case RenderingMode.Default:
                        break;

                    default:
                        break;
                }

            }
            catch (Exception e)
            {
                Debug.WriteLine("Exception Occured : " + e.Message);
            }
        }

        private bool _isBitmapInitialized;

        public bool IsBitmapInitialized
        {
            get { return _isBitmapInitialized; }
            set { _isBitmapInitialized = value; }
        }

        public void Invalidate()
        {
            this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(delegate
            {
                Draw();
            }));
        }
        public virtual void Draw()
        {
        }
        public void DrawPath(System.Windows.Media.PathGeometry seriesData, System.Drawing.Pen gdiPen, System.Drawing.Brush gdiBrush)
        {
            foreach (PathFigure figure in seriesData.Figures)
            {
                var points = figure.Segments.Cast<LineSegment>().Select(s => s.Point.AsDrawingPoint());

                var nextPoint = points.First();

                foreach (var point in points)
                {
                    var lastPoint = nextPoint;
                    nextPoint = point;
                    GDIGraphics.DrawLine(gdiPen, lastPoint, nextPoint);
                }
            }
        }
        internal void Initialize()
        {
            if (ActualHeight > 1 && ActualWidth > 1)
            {
                try
                {
                    System.Windows.Media.Matrix m = PresentationSource.FromVisual(this).CompositionTarget.TransformToDevice;
                    dpiFactor = 1 / m.M11;

                    switch (RenderingMode)
                    {
                        case RenderingMode.GDIRendering:
                            uint byteCount = (uint)(ActualWidth * ActualHeight * bpp);

                            //Allocate and create the InteropBitmap
                            var fileMappingPointer = CreateFileMapping(new IntPtr(-1), IntPtr.Zero, PAGE_READWRITE, 0, byteCount, null);
                            this.MapViewPointer = MapViewOfFile(fileMappingPointer, FILE_MAP_ALL_ACCESS, 0, 0, byteCount);
                            var format = PixelFormats.Bgra32;
                            var stride = (int)((int)ActualWidth * (int)format.BitsPerPixel / 8);
                            this.InteropBitmap = Imaging.CreateBitmapSourceFromMemorySection(fileMappingPointer,
                                                                                        (int)ActualWidth,
                                                                                        (int)ActualHeight,
                                                                                        format,
                                                                                        stride,
                                                                                        0) as InteropBitmap;
                            this.GDIGraphics = GetGdiGraphics(MapViewPointer);
                            break;  
          
                        case RenderingMode.WritableBitmap:
                            //Allocate and create the WritableBitmap
                            WritableBitmap = new WriteableBitmap((int)ActualWidth, (int)ActualHeight, 96, 96, PixelFormats.Bgra32, null);
                            ImageBitmap = new Bitmap((int)ActualWidth, (int)ActualHeight, ((int)ActualWidth * 4), System.Drawing.Imaging.PixelFormat.Format32bppPArgb, WritableBitmap.BackBuffer);
                            WritableBitmapGraphics = System.Drawing.Graphics.FromImage(ImageBitmap);
                            WritableBitmapGraphics.CompositingMode = this.CompositingMode.AsDrawingCompositingMode();
                            WritableBitmapGraphics.CompositingQuality = this.CompositingQuality.AsDrawingCompositingQuality();
                            WritableBitmapGraphics.SmoothingMode = this.SmoothingMode.AsDrawingSmoothingMode();
                            break;
                        default:
                            break;
                    }

                    Clear();
                    this.IsBitmapInitialized = true;
                }
                catch (OutOfMemoryException)
                {
                    throw new OutOfMemoryException();
                }
                catch (Exception e)
                {
                    Debug.WriteLine("Exception Occured : " + e.Message);
                }

            }

        }

        public void InvalidateBitmap()
        {
            switch (RenderingMode)
            {
                case RenderingMode.Default:
                    break;
                case RenderingMode.GDIRendering:
                    this.bitmapImage.Source = (BitmapSource)InteropBitmap.GetAsFrozen();
                    break;
                case RenderingMode.WritableBitmap:
                    this.bitmapImage.Source = (BitmapSource)WritableBitmap.GetAsFrozen();
                    break;
                default:
                    break;
            }
            this.bitmapImage.InvalidateVisual();  
        }
        private int index;
        internal int Index
        {
            get { return index; }
            set { index = value; }
        }
        private Graphics GetGdiGraphics(IntPtr mapViewPointer)
        {
            Graphics gdiGraphics;
            //create the GDI Bitmap pointing to the same part of memory.
            System.Drawing.Bitmap gdiBitmap;
            gdiBitmap = new System.Drawing.Bitmap((int)ActualWidth,
                                                  (int)ActualHeight,
                                                  (int)ActualWidth * bpp,
                                                  System.Drawing.Imaging.PixelFormat.Format32bppPArgb,
                                                  mapViewPointer);          
            // Get GDI Graphics 
            gdiGraphics = System.Drawing.Graphics.FromImage(gdiBitmap);
          
            gdiGraphics.CompositingMode = this.CompositingMode.AsDrawingCompositingMode();
            gdiGraphics.CompositingQuality = this.CompositingQuality.AsDrawingCompositingQuality();
            gdiGraphics.SmoothingMode = this.SmoothingMode.AsDrawingSmoothingMode();

            return gdiGraphics;
        }

        protected override Size MeasureOverride(Size constraint)
        {
            Size desiredSize = new Size(0, 0);
            int count = 0;

            foreach (UIElement child in Children)
            {
                //child.   
                Canvas.SetZIndex(child, count);
                count++;
                child.Measure(constraint);
                desiredSize.Width += child.DesiredSize.Width;
                desiredSize.Height += child.DesiredSize.Height;
            }
            if (Double.IsInfinity(constraint.Height))
                constraint.Height = desiredSize.Height;
            if (Double.IsInfinity(constraint.Width))
                constraint.Width = desiredSize.Width;
            return constraint;
        }

        protected override Size ArrangeOverride(Size arrangeSize)
        {
            foreach (UIElement child in Children)
            {
                child.Arrange(new Rect(new Point(0, 0), arrangeSize));
            }
            return arrangeSize;
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA2101:SpecifyMarshalingForPInvokeStringArguments", MessageId = "5"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass"), DllImport("kernel32.dll", SetLastError = true)]
        private static extern IntPtr CreateFileMapping(IntPtr hFile,
                                                       IntPtr lpFileMappingAttributes,
                                                       uint flProtect,
                                                       uint dwMaximumSizeHigh,
                                                       uint dwMaximumSizeLow,
                                                       string lpName);

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Portability", "CA1901:PInvokeDeclarationsShouldBePortable", MessageId = "4"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass"), DllImport("kernel32.dll", SetLastError = true)]
        private static extern IntPtr MapViewOfFile(IntPtr hFileMappingObject,
                                                   uint dwDesiredAccess,
                                                   uint dwFileOffsetHigh,
                                                   uint dwFileOffsetLow,
                                                   uint dwNumberOfBytesToMap);
        private void UpdateContainers()
        {
            axisLinesconatiner.Height = this.ActualHeight;
            axisLinesconatiner.Width = this.ActualWidth;
            foreach (var container in Containers)
            {
                container.RenderingMode = this.RenderingMode;
                container.dpiFactor = this.dpiFactor;
                //container.Height = this.ActualHeight;
                //container.Width = this.ActualWidth;
                switch (RenderingMode)
                {
                    case RenderingMode.GDIRendering:
                        container.InteropBitmap = this.InteropBitmap;
                        container.GDIGraphics = this.GDIGraphics;
                        break;
                    case RenderingMode.Default:
                        break;
                   case RenderingMode.WritableBitmap:
                        container.WritableBitmap = this.WritableBitmap;
                        container.WritableBitmapGraphics = this.WritableBitmapGraphics;
                        container.ImageBitmap = this.ImageBitmap;
                        break;
                    default:
                        break;
                }
            }
        }
        private void GenerateConatiners()
        {
            this.Children.Clear();
            this.Containers.Clear();
            axisLinesconatiner = new ContainerAxisCrossLines();
            Binding xAxisBinding = new Binding();
            xAxisBinding.Path = new PropertyPath("XAxis");
            xAxisBinding.Source = this;
            Binding yAxisBinding = new Binding();
            yAxisBinding.Path = new PropertyPath("YAxis");
            yAxisBinding.Source = this;
            axisLinesconatiner.SetBinding(ContainerAxisCrossLines.XAxisProperty, xAxisBinding);
            axisLinesconatiner.SetBinding(ContainerAxisCrossLines.YAxisProperty, yAxisBinding);
            axisLinesconatiner.Height = this.ActualHeight;
            axisLinesconatiner.Width = this.ActualWidth;
            foreach (var BaseSeries in Series)
            {
                SeriesContainer container = BaseSeries.CreateContainer();
                BaseSeries.Height = this.ActualHeight;
                BaseSeries.Width = this.ActualWidth;
                container.Series = BaseSeries;
                container.Container = this;
                container.RenderingMode = this.RenderingMode;
                container.dpiFactor = this.dpiFactor;      

                container.collection = this;
                switch (RenderingMode)
                {
                    case RenderingMode.GDIRendering:
                        container.InteropBitmap = this.InteropBitmap;
                        container.GDIGraphics = this.GDIGraphics;
                        break;
                   case RenderingMode.WritableBitmap:
                        container.WritableBitmap = this.WritableBitmap;
                        container.WritableBitmapGraphics = this.WritableBitmapGraphics;
                        container.ImageBitmap = this.ImageBitmap;
                        break;
                    case RenderingMode.Default:
                        this.Children.Add(container.PartsCanvas);
                        break;
                    default:
                        break;
                }

                this.Containers.Add(container);
            }

            this.Children.Add(bitmapImage);
            this.Children.Add(axisLinesconatiner);

            isIntialized = true;
        }

        internal SmoothingMode SmoothingMode
        {
            get { return (SmoothingMode)GetValue(SmoothingModeProperty); }
            set { SetValue(SmoothingModeProperty, value); }
        }
        public static readonly DependencyProperty SmoothingModeProperty =DependencyProperty.Register("SmoothingMode", typeof(SmoothingMode), typeof(ContainerCollection), new PropertyMetadata(SmoothingMode.HighQuality));



        internal CompositingQuality CompositingQuality
        {
            get { return (CompositingQuality)GetValue(CompositingQualityProperty); }
            set { SetValue(CompositingQualityProperty, value); }
        }
        public static readonly DependencyProperty CompositingQualityProperty =DependencyProperty.Register("CompositingQuality", typeof(CompositingQuality), typeof(ContainerCollection), new PropertyMetadata(CompositingQuality.HighQuality));

         internal CompositingMode CompositingMode
        {
            get { return (CompositingMode)GetValue(CompositingModeProperty); }
            set { SetValue(CompositingModeProperty, value); }
        }
        public static readonly DependencyProperty CompositingModeProperty =DependencyProperty.Register("CompositingMode", typeof(CompositingMode), typeof(ContainerCollection), new PropertyMetadata(CompositingMode.SourceOver));


        public RenderingMode RenderingMode
        {
            get { return (RenderingMode)GetValue(RenderingModeProperty); }
            set { SetValue(RenderingModeProperty, value); }
        }

        public static readonly DependencyProperty RenderingModeProperty =
            DependencyProperty.Register("RenderingMode", typeof(RenderingMode), typeof(ContainerCollection), new PropertyMetadata(RenderingMode.Default));



        public Chart Chart
        {
            get { return (Chart)GetValue(ChartProperty); }
            set { SetValue(ChartProperty, value); }
        }
        public static readonly DependencyProperty ChartProperty =DependencyProperty.Register("Chart", typeof(Chart), typeof(ContainerCollection), new PropertyMetadata(null));



        public XAxis XAxis
        {
            get { return (XAxis)GetValue(XAxisProperty); }
            set { SetValue(XAxisProperty, value); }
        }
        public static readonly DependencyProperty XAxisProperty =DependencyProperty.Register("XAxis", typeof(XAxis), typeof(ContainerCollection), new PropertyMetadata(null));

        public YAxis YAxis
        {
            get { return (YAxis)GetValue(YAxisProperty); }
            set { SetValue(YAxisProperty, value); }
        }
        public static readonly DependencyProperty YAxisProperty = DependencyProperty.Register("YAxis", typeof(YAxis), typeof(ContainerCollection), new PropertyMetadata(null));

    }
}
